﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;

namespace MoneyWentWhere.DataModel.Reports
{
    /// <summary>
    /// Encapsulates an elementary unit of information within a report,
    /// an amount of money spend within a single period
    /// </summary>
    public class PeriodAmountKey : IComparable
    {
        public readonly DateTime StartDate; 
        public readonly TimeSpanUnit DurationUnit;
        public readonly Account Account;
        public readonly TransactionKind Kind;
        public readonly bool IsDeposit;
        public readonly String Category;

        readonly object[] HashElems;

        public PeriodAmountKey(DateTime startDate, TimeSpanUnit durationUnit, 
            Account account, TransactionKind kind, bool isDeposit, String category)
        {
            StartDate = startDate;
            DurationUnit = durationUnit;
            Account = account;
            Kind = kind;
            IsDeposit = isDeposit;
            Category = category;

            HashElems = new object[] { StartDate, Duration, Account, Kind, IsDeposit, Category };
        }

        public PeriodAmountKey(Transaction tx, TimeSpanUnit durationUnit) 
        {
            StartDate = tx.Date.PeriodStart(durationUnit);
            DurationUnit = durationUnit;
            Account = tx.Account;
            Kind = tx.Kind;
            IsDeposit = tx.Amount >= 0;
            Category = tx.Category;
        }

        // Convenience properties
        public TimeSpan Duration { get { return StartDate.PeriodDuration(DurationUnit); } }
        public DateTime NextPeriodStart { get { return StartDate + Duration; } }
        public Currency Currency { get { return Account.Currency; } }

        public override bool Equals(object obj)
        {
            PeriodAmountKey that = obj as PeriodAmountKey;
            if (that == null) { return false; }

            return
                StartDate.Equals(that.StartDate) &&
                Duration.Equals(that.Duration) &&
                Account.Equals(that.Account) &&
                Kind.Equals(that.Kind) &&
                IsDeposit.Equals(that.IsDeposit) &&
                Category.Equals(that.Category);
        }

        public override int GetHashCode()
        {
            object[] hashElems = new object[] { StartDate, Duration, Account, Kind, IsDeposit, Category };

            int result = 17;
            foreach (object item in hashElems)
            {
                result = result * 31 + item.GetHashCode();
            }
            return result;
        }

        public override string ToString()
        {
            return String.Format("{0:yyyy-MM-dd} {1} {2}/{3} {4} {5}{6}",
                StartDate,
                DurationUnit,
                Account.Name,
                Account.Currency,
                Kind,
                Category,
                IsDeposit ? "+" : "-");
        }


        public int CompareTo(object obj)
        {
            PeriodAmountKey that = obj as PeriodAmountKey;
            if (that == null) { return -1; }

            int c;
            c = StartDate.CompareTo(that.StartDate);
            if (c != 0) { return c; }
            c = Account.Currency.Id.CompareTo(that.Account.Currency.Id);
            if (c != 0) { return c; }
            c = Account.Name.CompareTo(that.Account.Name);
            if (c != 0) { return c; }
            c = Kind.CompareTo(that.Kind);
            if (c != 0) { return c; }
            c = Category.CompareTo(that.Category);
            if (c != 0) { return c; }
            c = IsDeposit.CompareTo(that.IsDeposit);

            return c;
        }

    }

    public class PeriodAmount : IComparable
    {
        public readonly PeriodAmountKey Key;
        public readonly List<Transaction> Transactions = new List<Transaction>();

        public PeriodAmount(PeriodAmountKey key)
        {
            Key = key;
        }

        public void Add(Transaction tx)
        {
            if (tx.Date < Key.StartDate) { throw new ArgumentException("Before start date: " + tx); }
            if (tx.Date >= Key.NextPeriodStart) { throw new ArgumentException("After end date: " + tx); }
            if (tx.Account != Key.Account) { throw new ArgumentException("Account not same: " + tx); }
            if (tx.Kind != Key.Kind) { throw new ArgumentException("Kind not same: " + tx); }
            if ((tx.Amount >= 0) != Key.IsDeposit) { throw new ArgumentException("IsDeposit not same: " + tx); }
            if (tx.Category != Key.Category) { throw new ArgumentException("Category not same: " + tx); }

            Transactions.Add(tx);
        }

        public decimal Amount
        {
             get 
             {
                 decimal amount = 0;
                 foreach (Transaction t in Transactions)
                 {
                     amount += t.Amount;
                 }
                 return amount;
             }
        }

        #region IComparable Members

        public int CompareTo(object obj)
        {
            PeriodAmount that = obj as PeriodAmount;
            if (that == null) return -1;

            int c = Key.CompareTo(that.Key);
            if (c != 0) { return c; }
            c = Math.Abs(Amount).CompareTo(Math.Abs(that.Amount));

            return c;
        }

        #endregion
    }


}
